/*
 * Copyright (c) 2008 Kevin Wetzels
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package be.roam.jindy.ui.pagination;

import static java.lang.Math.ceil;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Class that handles pagination.
 * <p>
 * This class can be used in for-each statements like this:
 * <code>for (Page page : pagination) { ... }</code>
 * </p>
 * 
 * @author Kevin Wetzels
 * 
 */
public class Pagination implements Serializable, Iterable<Page> {

	private static final long serialVersionUID = 6056547853074232751L;

	private int totalNrItems;

	private int nrItemsPerPage;

	private int currentPageNr;

	private int nrPages;

	private transient Page first;

	private transient Page last;

	private transient Page current;

	/**
	 * Pagination constructor.
	 * 
	 * @param currentPageNr
	 *            the current page number (starting at 1)
	 * @param nrItemsPerPage
	 *            the number of items per page
	 * @param totalNrItems
	 *            the total number of items
	 */
	public Pagination(int currentPageNr, int nrItemsPerPage, int totalNrItems) {
		this.currentPageNr = currentPageNr;
		this.nrItemsPerPage = nrItemsPerPage;
		this.totalNrItems = totalNrItems;
		this.nrPages = (int) ceil((double) totalNrItems / (double) nrItemsPerPage);
	}

	/**
	 * Checks if the current page number is in fact valid.
	 * 
	 * @return <code>false</code> when the current page number is less than 1 or
	 *         larger than the total number of pages
	 */
	public boolean isValidCurrentPageNr() {
		return (currentPageNr > 0 && currentPageNr <= nrPages);
	}

	/**
	 * @return the total number of items
	 */
	public int getTotalNrItems() {
		return totalNrItems;
	}

	/**
	 * @return the number of items to display on each page
	 */
	public int getNrItemsPerPage() {
		return nrItemsPerPage;
	}

	/**
	 * @return the number of the current page
	 */
	public int getCurrentPageNr() {
		return currentPageNr;
	}

	/**
	 * @return the number of pages
	 */
	public int getNrPages() {
		return nrPages;
	}

	/**
	 * Creates a list of page objects making up the pagination.
	 * <p>
	 * Warning: this method will create a page object for each page. If you
	 * suspect there are going to be a lot of pages, consider using
	 * {@link #getFirst()}, {@link #getLast()} or {@link #getCurrent()} instead.
	 * </p>
	 * 
	 * @return list of page objects making up the pagination
	 */
	public List<Page> getPages() {
		List<Page> pages = new ArrayList<Page>(nrPages);
		Iterator<Page> iterator = iterator();
		while (iterator.hasNext()) {
			pages.add(iterator.next());
		}
		return pages;
	}

	/**
	 * Creates the first page object.
	 * <p>
	 * Use this method if you suspect you're going to be dealing with a lot of
	 * pages, since this will only create the page object when it's requested,
	 * unlike the {@link #getPages()} method which will create all page objects.
	 * </p>
	 * 
	 * @return the first page object
	 */
	public Page getFirst() {
		if (first == null) {
			first = new Page(this, 1);
			if (last == null && isSinglePage()) {
				// We don't need two objects to point to the same thing
				last = first;
			}
		}
		return first;
	}

	/**
	 * Creates the last page object.
	 * <p>
	 * Use this method if you suspect you're going to be dealing with a lot of
	 * pages, since this will only create the page object when it's requested,
	 * unlike the {@link #getPages()} method which will create all page objects.
	 * </p>
	 * 
	 * @return the last page object
	 */
	public Page getLast() {
		if (last == null) {
			last = new Page(this, nrPages);
			if (first == null && isSinglePage()) {
				// We don't need two objects to point to the same thing
				first = last;
			}
		}
		return last;
	}

	/**
	 * Creates a page object for the current page.
	 * <p>
	 * Use this method if you suspect you're going to be dealing with a lot of
	 * pages, since this will only create the page object when it's requested,
	 * unlike the {@link #getPages()} method which will create all page objects.
	 * </p>
	 * 
	 * @return the current page object
	 */
	public Page getCurrent() {
		if (current == null) {
			current = new Page(this, getCurrentPageNr());
		}
		return current;
	}

	/**
	 * @return <code>true</code> when there is only one page to display
	 */
	public boolean isSinglePage() {
		return nrPages == 1;
	}

	/**
	 * Returns a {@link PageIterator}.
	 */
	@Override
	public Iterator<Page> iterator() {
		return new PageIterator(this);
	}

	/**
	 * Synonym for {@link #iterator()} that avoids that code which relies on
	 * getters for properties breaks.
	 * 
	 * @return the iterator
	 */
	public Iterator<Page> getIterator() {
		return iterator();
	}

}
